local _, gotGestures = ...
_G["gotGestures"] = gotGestures

-- See if user has LibShortcuts running
local libShortcuts
if LibStub then
	libShortcuts = LibStub:GetLibrary("LibShortcuts-1.1", true)
end

-- Constants
BINDING_HEADER_gotGestures = "gotGestures"
local PLAYERNAME = string.format("%s - %s", UnitName("player"), GetRealmName())

-- Compass rose directions.
local Directions = {}
Directions[45] = {"U-R", "R", "D-R", "D", "D-L", "L", "U-L", "U"}
Directions[90] = {"R", "D", "L", "U"}


local Button = CreateFrame("Button", "gotGesturesButton")
gotGestures.Button = Button
Button:Hide()
Button.functions = {}

-- Functions executed by the keybind.
function Button:Down()
	self:Show()
	gotGestures:Fire("PathStart")
end

function Button:Up()
	self:Hide()
	gotGestures:Fire("PathStop")
end

-- Some settings.
local defaults = {
interval = 0.1,
minDist = 40,
step = 90,
}

-- Pull in some functions we'll be using a lot.
local sqrt = math.sqrt
local abs = math.abs
local atan2 = atan2
local floor = floor

-- Helper functions.
local function CalculateDistance(dx, dy)
	return abs(sqrt(dx^2 + dy^2))
end

local function CalculateDirection(bearing, step)
	local dir = floor(bearing / step + 0.5)
	if dir == 0 then
		dir = #Directions[step]
	end
	
	return Directions[step][dir]
end

local function CalculateBearing(dx, dy)
	local bearing = atan2(dx, dy)
	
	if bearing < 0 then
		bearing = bearing + 360
	end
	
	return bearing
end

-- OnUpdate polling of GetCursorPosition to track the cursor path.
local function UpdateTracking(self, elapsed)
	self.elapsed = self.elapsed + elapsed
	if self.elapsed >= self.db.settings.interval then
		local x, y, lastx, lasty
		lastx, lasty = self.lastx, self.lasty
		x,y = GetCursorPosition()
		
		if not (lastx and lasty) then
			self.lastx, self.lasty = x, y
		else
			local dx, dy = x-lastx, y-lasty
			local dist = CalculateDistance(dx, dy)
			
			if dist > self.db.settings.minDist then
				local bearing = CalculateBearing(dx, dy)
				local dir = CalculateDirection(bearing, self.db.settings.step)
				
				if dir ~= self.lastDir then
					self.lastDir = dir
					self.path = self.path .. dir
					gotGestures:Fire("UpdatePath", self.path)
				end
				
				self.lastx, self.lasty = x, y
			end
		end
		
		self.elapsed = 0
	end
end

Button:SetScript("OnShow", function(self) 
	self.lastDir = nil
	self.elapsed = 0 
	self.path = "" 
	self.lastx, self.lasty = nil, nil 
end)
Button:SetScript("OnHide", function(self) 
	if self.path and self.path ~= "" then
		if not self.menuOverride then
			local action = self.db.binds[PLAYERNAME][self.path]
			
			if action and self.functions[action] then
				self.functions[action](self.db.scripts[action].args)
			end
		end
	end
end)
Button:SetScript("OnUpdate", UpdateTracking)

function Button:InitializeDB()
	if not gotGesturesDB then
		gotGesturesDB = {}
	end
	db = gotGesturesDB
	
	if not db.binds then
		db.binds = {}
	end
	
	if not db.scripts then
		db.scripts = {}
	end
	
	if not db.settings then
		db.settings = {}
	end
	
	setmetatable(db.settings, {__index = defaults})
	
	-- Check if profile exists, create one if not.
	if not db.binds[PLAYERNAME] then
		db.binds[PLAYERNAME] = {}
	end
	
	self.db = db
	gotGestures.db = db
	gotGestures.binds = db.binds[PLAYERNAME]
	
	self.functions = {}
	gotGestures.functions = self.functions
	
	gotGestures:AddFunction("gotGestures config", "InterfaceOptionsFrame_OpenToCategory", "gotGestures")
	gotGestures.initiated = true
	gotGestures:Fire("Initiated")
end

function Button:MakeFuncs()
	for name, data in pairs(self.db.scripts) do
		if data.func then
			self.functions[name] = _G[data.func]
			data.enabled = not not _G[data.func]
		else
			self.functions[name] = loadstring(data.code)
		end
	end
end

Button:SetScript("OnEvent", function(self, event, ...)
	if type(self[event]) == "function" then
		self[event](self, event, ...)
	end
end)

function Button:ADDON_LOADED(event, addon)
	if addon == "gotGestures" then
		self:InitializeDB()
		self:UnregisterEvent(event)
	end
end

function Button:PLAYER_ENTERING_WORLD(event)
	self:MakeFuncs()
	if libShortcuts then
	
	end
	
	self:UnregisterEvent(event)
end
Button:RegisterEvent("ADDON_LOADED")
Button:RegisterEvent("PLAYER_ENTERING_WORLD")

gotGestures.events = {}
function gotGestures:Fire(event, ...)
	if self.events[event] then
		for i=1, #self.events[event] do
			self.events[event][i](self, event, ...)
		end
	end
end

function gotGestures:IsListening(event, func)
	for i=1, #self.events[event] do
		if self.events[event][i] == func then
			return true
		end
	end
end

function gotGestures:Listen(event, func)
	if not self.events[event] then	
		self.events[event] = {}
	end
	
	if not self:IsListening(event, func) then
		table.insert(self.events[event], func)
	end
end

function gotGestures:GetBindingForScript(binds, script)
	if type(binds) ~= "table" then
		return
	end
	
	for k,v in pairs(binds) do
		if v == script then
			return k
		end
	end
end

function gotGestures:AddScriptTable(t)
	for scriptname, data in pairs(t) do
		local success
		if data.func then
			success = self:AddFunction(scriptname, t.func, t.args)
		elseif data.code then
			success = self:AddFunction(scriptname, t.code)
		end
		
		if success and self.initiated and data.binding then
			self.binds = data.binding
		end
	end
end

function gotGestures:AddFunction(name, func, args)
	if not name or name == "" or not func or func == "" then
		return print("Name or function was nil.")
	end
	
	local script = self.db.scripts[name] or {}
	local functions = self.functions
	
	-- Check if the function exists in global scope
	if _G[func] then
		functions[name] = _G[func]
		script.func = func
		script.code = nil
		script.args = args ~= "" and args or nil
	else
		return print(string.format("Function %q was not found in global scope.", func))
	end
	
	self.db.scripts[name] = script
	return true
end

function gotGestures:AddCode(name, code)
	if not name or name == "" or not code or code == "" then
		return print("Name or code was nil.")
	end
	
	local f, err = loadstring(code)
	if not f then
		return print("Error loading code: "..err)
	end
	
	local script = self.db.scripts[name] or {}
	script.code = code
	script.func = nil
	self.functions[name] = f
	
	self.db.scripts[name] = script
	return true
end

function gotGestures:DeleteScript(name)
	if not name or name == "" then
		return print("Thar be no script.")
	end
	
	local bind = self:GetBindingForScript(self.binds, name)
	if bind then
		self.binds[bind] = nil
	end
	
	self.db.scripts[name] = nil
	self.functions[name] = nil
end